/** Fichier implémentant la forme d'un cône infini.
 *
 * Ce fichier implémente la classe BaseCone.
 *
 * @file BaseCone.cpp
 * @author Johan "Solo" Luisier
 *
 * @date 4 Juillet 2011
 */
#include "BaseCone.hpp"

#include "Algebre/Solveur.hpp"

#include "Utils/Erreur.hpp"

#include <cmath>
#include <cstdlib>

#include <iostream>

#include <map>

using namespace std;

/** Constructeur standard.
 *
 * Constructeur permettant d'initialiser tous les champs du BaseCone. La
 * transformation nécessaire pour passer en coordonnées locales est
 * initialisée ici. Comme pour Tore, on a un maximum de trois essais,
 * qui peut conduire à un arrêt du programme si tous échouent.
 *
 * Le Solveur utilisé pour la recherche d'intersection est initialisé
 * ici.
 *
 * @todo Utiliser la fonction appropriée de ConstruitMatrice.hpp au lieu de
 * faire les tests à la main.
 *
 * @param[in] pt sommet du BaseCone.
 * @param[in] axe axe de symétrie du BaseCone.
 * @param[in] ouverture tangente de l'angle d'ouverture.
 */
BaseCone::BaseCone( const Coord& pt, const VecteurU& axe,
                    const double& ouverture )
    : FormeInfinie( "BaseCône" ), Axe( axe ), Sommet( pt ), TanAlpha( ouverture )
{
    /*
     * On initialise la transformation ici
     */
    if ( axe != VecteurU( 0., 0., 1. ) )
    {
        VecteurU xPrime, yPrime;
        MatriceSO3 test;
        try
        {
            xPrime = VecteurU( axe.y(), -axe.x(), 0 );
            yPrime = axe ^ xPrime;
            test = MatriceSO3( xPrime, yPrime, Axe );
        }
        catch(Erreur& e)
        {
            try
            {
                xPrime = VecteurU( axe.z(), 0, -axe.x() );
                yPrime = axe ^ xPrime;
                test = MatriceSO3( xPrime, yPrime, Axe );
            }
            catch(Erreur& e)
            {
                try
                {
                    xPrime = VecteurU( 0, axe.z(), -axe.y() );
                    yPrime = axe ^ xPrime;
                    test = MatriceSO3( xPrime, yPrime, Axe );
                }
                catch(Erreur& e)
                {
                    cerr << "On a tout essayé, mais ça marche pas!" << endl;
                    exit( 1 );
                }
            }
            cout << "x' = " << xPrime << endl;
        }

        VersLocal  = Transformation( test, Sommet, 1., false );
        VersGlobal = VersLocal.inverse();
    }

    MoteurResolution = new Solveur();
}

/** Constructeur de copie, permettant de dupliquer un BaseCone.
 *
 * @param[in] baseCone objet que l'on veut copier.
 */
BaseCone::BaseCone( const BaseCone& baseCone)
    : FormeInfinie( "BaseCone", baseCone.VersGlobal ), Axe( baseCone.Axe ),
      Sommet( baseCone.sommet() ), TanAlpha( baseCone.TanAlpha )
{
    MoteurResolution = new Solveur();
}

/** Destructeur, virtuel pour assurer une séquence de destruction correcte.
 *
 * Le destructeur doit libérer la mémoire allouée pour le solveur,
 */
BaseCone::~BaseCone()
{}

/** Calcule l'intersection entre le rayon et le cône infini formé par
 * l'instance.
 *
 * L'intersection est calculée dans les coordonées locales, ce qui donne une
 * équation du deuxième degré à résoudre. Une fois la solution identifiée on
 * fait la transformation dans les coordonnéées globales.
 *
 * @param[in] ray instance de rayon avec lequel il faut trouver l'intersection.
 *
 * @retval NullePart si l'intersection n'existe pas, @retval inter la
 * coordonnée du point d'intersection.
 */
Coord BaseCone::trouveIntersection( const Rayon& ray ) const
{
    VecteurU dir( ray.direction() );
    Coord origine( ray.origine() );

    dir     = VersLocal( dir );
    origine = VersLocal( origine );

    double a( pow( dir.x(), 2 ) + pow( dir.y(), 2 )
              - pow( dir.z() * TanAlpha, 2 ) );
    double b( 2. * ( dir.x() * origine.x() + dir.y() * origine.y()
                     - dir.z() * origine.z() * pow( TanAlpha, 2 ) ) );
    double c( pow( origine.x(), 2 ) + pow( origine.y(), 2 )
              - pow( origine.z() * TanAlpha, 2 ) );

    MoteurResolution -> setParams( c, b, a );

    vector< double > solutions;

    MoteurResolution -> resoud( solutions );

    // cerr << solutions.size() << " solution(s)" << endl;

    vector< double >::const_iterator It, Begin( solutions.begin() ),
            End( solutions.end() );

    Coord local;

    for (It = Begin; It != End; It++)
    {
        if ( *It > 1e-7 )
        {
            local = origine;
            local += (*It) * dir ;
            if ( local.z() < -1.e-7 ) continue;
            //cout << "En coordonnées locales : " << local << endl;
            return VersGlobal( local );
            //cout << "En coordonnées globales : " << local << endl;
        }
    }

    return NullePart;
}

/** Calcule la valeur du champ de normale au point donné.
 *
 * Ne vérifie pas si le point est bien à la surface de l'objet.
 *
 * @param[in] coord point de la surface.
 *
 * @return la normale au point @c coord.
 */
VecteurU BaseCone::normale( Coord const &coord ) const
{
    Vecteur pt( VersLocal( coord ) );

    Vecteur local( 2 * pt.x(),
                   2 * pt.y(),
                   2 * pt.z() * pow( TanAlpha, 2 ) );

    return VecteurU( VersGlobal( local) );
}

/** Permet de vérifier si un point est à @e l'intérieur de l'instance.
 *
 * On utilise l'équation du cône pour savoir si le point est bien dans le volume
 * délimité.
 *
 * @param[in] coord point à tester.
 *
 * @retval true si le point est bien dans le volume, @retval false sinon.
 */
bool BaseCone::estDans(Coord const &coord) const
{
    Coord pt( VersLocal( coord ) );

    return ( pow( pt.x(), 2 ) + pow( pt.y(), 2 ) <
             pow( TanAlpha * pt.z(), 2 ) );
}

/** Permet de vérifier si un point est à la @e surface de l'instance.
 *
 * On utilise l'équation du cône pour savoir si le point est à la surface.
 *
 * @param[in] coord point à tester
 *
 * @retval true si le point est bien sur la surface, @retval false sinon.
 */
bool BaseCone::estSur(Coord const& coord) const
{
    Coord pt( VersLocal( coord ) );

    return ( abs( pow( pt.x(), 2 ) + pow( pt.y(), 2  )
                  - pow( TanAlpha * pt.z(), 2 ) ) < 1.e-7 );
}

/** Méthode permettant d'appliquer une texture sur le cône.
 *
 * On récupère les coordonnées intrinsèques, que l'on traite pour déterminer le
 * pixel à mapper.
 *
 * Si le point en question ne peut être converti en coordonnées intrinsèques,
 * alors le pixel (0,0) est utilisé.
 *
 * @param[in] pt point de la surface à mapper.
 * @param[out] i coordonnée x du pixel de l'image à plaquer au point @c pt.
 * @param[out] j coordonnée y du pixel de l'image à plaquer au point @c pt.
 * @param[in] frequence fréquence de répétition de la texture.
 * @param[in] Hauteur hauteur (en pixels) de l'image à mapper.
 * @param[in] Largeur largeur (en pixels) de l'image à mapper.
 */
void BaseCone::mapping(const Coord &pt, int &i, int &j, const int &frequence,
                       const int &Hauteur, const int &Largeur)
const
{
    Coord carte( intrinseque( pt ) );

    if ( carte == NullePart )
    {
        i = j = 0;
        return;
    }

    double t;
    t = double( frequence * Hauteur ) * carte.x() / ( carte.z()  * 2. * Forme::ValeurPi );
    i = static_cast< int >( floor( t ) );

    t = double( frequence * Largeur ) * carte.y() / ( Hauteur * ( 1. +
                                                                  TanAlpha ) );
    j = static_cast< int >( floor( t ) );
}

/** Méthode affichant les paramètres de l'instance.
 *
 * Cette méthode affiche Sommet, Axe et TanAlpha.
 *
 * @param[in] os référence sur le flot de sortie à utiliser.
 *
 * @return une référence sur le flot de sortie utilisé.
 */
ostream& BaseCone::affiche(ostream& os) const
{
    os << "Forme : " << nom() << endl << "Paramètres :" << endl
       << "\tSommet  : " << Sommet << endl
       << "\tAxe     : " << Axe << endl
       << "\tAngle   : " << TanAlpha << endl;

    return os;
}

/** Méthode calculant les coordonnées intrinsèques d'un point de la surface.
 *
 * Si le point n'est pas sur la surface, on renvoie @c NullePart.
 *
 * On transforme le point de la surface en coordonnées locales. L'angle polaire
 * est ensuite calculé, ainsi que le rayon. On calcule en fait en pseudo
 * coordonnées cylindriques.
 *
 * @param[in] pt point de la surface.
 *
 * @retval NullPart si le point n'est pas à la surface, @retval local dont les
 * coordonnées sont @code ( theta * TanAlpha * z, r, TanAlpha * z ) @endcode.
 */
Coord BaseCone::intrinseque(const Coord& pt) const
{
    if ( ! estSur( pt ) )
        return NullePart;

    Coord local( VersLocal( pt ) );

    if ( local.r() < 1.e-7 )
        return Coord( 0., 0., 0. );

    double theta, l;

    l = sqrt( pow( local.x(), 2 ) + pow( local.y(), 2 ) );

    VecteurU unit( local );

    theta = acos( unit.x() );

    if ( local.y() < -1.e-7 )
        theta = 2 * Forme::ValeurPi - theta;

    return Coord( theta * TanAlpha * local.z(),
                  l, TanAlpha * local.z() );
}

/** Accesseur pour Sommet.
 *
 * Donne accès au sommet de l'instance.
 *
 * @return une référence constante sur Sommet
 */
const Coord& BaseCone::sommet() const
{
    return Sommet;
}

/** Accesseur pour Axe.
 *
 * Donne accès à l'axe de symétrie de l'instance.
 *
 * @return une férérence constante sur Axe.
 */
const VecteurU& BaseCone::axe() const
{
    return Axe;
}

/** Accesseur pour TanAlpha.
 *
 * Donne accès à la tangente de l'angle d'ouverture de l'instance.
 *
 * @return une référence constante sur TanAlpha.
 */
const double& BaseCone::tanAlpha() const
{
    return TanAlpha;
}
